package com.cmc.cloud.cmclink.doc.util;

import com.cmc.cloud.cmclink.doc.constants.errorconstant.ApiErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.CommonErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.VgmErrorCodeConstants;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

/**
 * 文件util
 *
 * @author dev89
 * @date 2025/10/02 10:06
 **/
@Slf4j
public class FileOperateUtil {
    public static final String SUFFIX_PDF = ".pdf";
    public static final String SUFFIX_XLS = ".xls";
    public static final String SUFFIX_XLSX = ".xlsx";
    private static final int CONNECT_TIMEOUT_MILLIS = 10000;
    private static final int READ_TIMEOUT_MILLIS = 10000;

    /**
     * 设置响应头
     */
    public static void setupResponseHeaders(HttpServletResponse response, String fileName, long contentLength)
            throws UnsupportedEncodingException {
        // 设置内容类型
        response.setContentType("application/octet-stream");

        // 设置内容长度
        if (contentLength > 0) {
            response.setContentLengthLong(contentLength);
        }

        // 设置文件名（处理中文文件名）
        String encodedFileName = java.net.URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);

        // 缓存控制
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
    }

    /**
     * 使用Apache Commons IO下载文件
     *
     * @param fileUrl  https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf
     * @param savePath savePath pdf/tmp/test.pdf
     */
    public static void downloadWithCommonsIo(String fileUrl, String savePath) {
        try {
            FileUtils.copyURLToFile(new URL(fileUrl), new File(savePath), CONNECT_TIMEOUT_MILLIS, READ_TIMEOUT_MILLIS);
        } catch (Exception e) {
            log.error("caught an exception on downloadWithCommonsIo", e);
            throw ExceptionUtil.exception(ApiErrorCodeConstants.DOWNLOAD_FILE_FAILED, e.toString());
        }
    }

    /**
     * 使用Apache Commons IO下载文件
     *
     * @param fileUrl  https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf
     * @param saveFile saveFile 保存文件对象
     */
    public static void downloadWithCommonsIo(String fileUrl, File saveFile) {
        try {
            FileUtils.copyURLToFile(new URL(fileUrl), saveFile, CONNECT_TIMEOUT_MILLIS, READ_TIMEOUT_MILLIS);
        } catch (Exception e) {
            log.error("caught an exception on downloadWithCommonsIo", e);
            throw ExceptionUtil.exception(ApiErrorCodeConstants.DOWNLOAD_FILE_FAILED, e.toString());
        }
    }

    /**
     * file转MultipartFile
     *
     * @param file file
     * @return org.springframework.web.multipart.MultipartFile
     */
    public static MultipartFile ofMultipartFile(File file) {
        FileItem fileItem =
                new DiskFileItemFactory().createItem("file", MediaType.MULTIPART_FORM_DATA_VALUE, true, file.getName());
        try (InputStream inputStream = Files.newInputStream(file.toPath());
             OutputStream outputStream = fileItem.getOutputStream()) {
            IOUtils.copy(inputStream, outputStream);
        } catch (Exception e) {
            log.error("caught an exception on ofMultipartFile", e);
            throw ExceptionUtil.exception(ApiErrorCodeConstants.TRANSIT_MULTIFILE_FAILED, e.toString());
        }
        return new CommonsMultipartFile(fileItem);
    }

    /**
     * 获取resource目录的绝对路径
     */
    public static String getResourcePath() {
        ClassLoader classLoader = FileOperateUtil.class.getClassLoader();
        URL resourceUrl = classLoader.getResource("");

        if (resourceUrl != null) {
            return resourceUrl.getPath();
        } else {
            throw ServiceExceptionUtil.exception(ApiErrorCodeConstants.GET_RESOURCE_PATH_FAILED);
        }
    }

    /**
     * 下载文件模版，仅适用.xls
     *
     * @param fileName     文件名称
     * @param templatePath 模板路径
     * @param response     输出流
     * @return CommonResult
     */
    public static CommonResult<Void> downloadExcelTemplate(HttpServletResponse response, String fileName,
                                                           String templatePath) {
        response.reset();
        response.setContentType(getExcelContentType(fileName));
        response.setHeader("Content-Disposition", "attachment; filename=\"" + encodeFilename(fileName) + "\"");

        // 通过类加载器读取resources目录下的文件
        try (InputStream inputStream = FileOperateUtil.class.getClassLoader().getResourceAsStream(templatePath);
             BufferedInputStream bis = new BufferedInputStream(inputStream);
             BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
            if (inputStream == null) {
                throw ServiceExceptionUtil.exception(VgmErrorCodeConstants.VGM_TEMPLATE_NOT_EXIST);
            }
            byte[] buffer = new byte[4096];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            // 关闭流
            outputStream.flush();
        } catch (Exception e) {
            log.info(e.getMessage());
            throw ServiceExceptionUtil.exception(CommonErrorCodeConstants.EXCEL_DOWNLOAD_FAILED);
        }
        return CommonResult.success();
    }

    private static String getExcelContentType(String fileName) {
        if (fileName.endsWith(SUFFIX_XLSX)) {
            return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        } else if (fileName.endsWith(SUFFIX_XLS)) {
            return "application/vnd.ms-excel";
        } else {
            return "application/octet-stream";
        }
    }

    private static String encodeFilename(String filename) {
        try {
            return URLEncoder.encode(filename, "UTF-8").replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            return filename;
        }
    }

    /**
     * 合并指定文件列表中的PDF文件
     *
     * @param inputFiles 输入PDF文件列表
     * @param outputFile 输出合并后的PDF文件
     * @return 合并成功返回true，否则返回false
     */
    public static boolean mergePdfFiles(List<File> inputFiles, File outputFile) {
        if (inputFiles == null || inputFiles.isEmpty()) {
            System.err.println("输入文件列表为空");
            return false;
        }

        PDFMergerUtility merger = new PDFMergerUtility();

        try {
            // 添加所有源文件
            for (File file : inputFiles) {
                if (file.exists() && file.isFile() && file.getName().toLowerCase().endsWith(SUFFIX_PDF)) {
                    merger.addSource(file);
                    System.out.println("添加文件: " + file.getName());
                } else {
                    System.err.println("跳过无效文件: " + file.getPath());
                }
            }

            // 设置输出文件
            merger.setDestinationFileName(outputFile.getAbsolutePath());

            // 执行合并操作
            merger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());

            System.out.println("PDF文件合并成功! 输出文件: " + outputFile.getAbsolutePath());
            return true;

        } catch (IOException e) {
            System.err.println("PDF文件合并失败: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 合并指定目录中的所有PDF文件
     *
     * @param directory  包含PDF文件的目录
     * @param outputFile 输出合并后的PDF文件
     * @return 合并成功返回true，否则返回false
     */
    public static boolean mergePdfsInDirectory(File directory, File outputFile) {
        if (directory == null || !directory.exists() || !directory.isDirectory()) {
            System.err.println("目录不存在或不是有效目录");
            return false;
        }

        File[] files = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(SUFFIX_PDF));

        if (files == null || files.length == 0) {
            System.err.println("目录中没有找到PDF文件");
            return false;
        }

        List<File> pdfFiles = new ArrayList<>();
        for (File file : files) {
            if (file.isFile()) {
                pdfFiles.add(file);
            }
        }

        return mergePdfFiles(pdfFiles, outputFile);
    }

    /**
     * 使用FileInputStream将文件读取为byte[]
     *
     * @param file 文件路径
     * @return 文件的字节数组
     */
    public static byte[] readFileToByteArrayWithFileInputStream(File file) {

        try (FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            return bos.toByteArray();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * byte[] 写入文件
     *
     * @param file 文件路径
     * @return 文件的字节数组
     */
    public static void readBytesFileToFile(byte[] fileData, File file) {
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        try (FileOutputStream fos = new FileOutputStream(file);) {
            fos.write(fileData);
            fos.flush();
            ;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 下载文件并重命名
     *
     * @param fileName 文件名
     * @param filePath 文件路径
     * @param response response
     */
    public static void downloadFileWithCustomName(String filePath, String fileName, HttpServletResponse response) {
        try {
            File file = new File(filePath);

            if (!file.exists()) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("File not found: " + filePath);
                return;
            }

            // 使用自定义文件名
            setupResponseHeaders(response, fileName, file.length());

            Files.copy(file.toPath(), response.getOutputStream());
            response.flushBuffer();

        } catch (Exception e) {
            log.error("caught an exception on downloadFileWithCustomName", e);
            throw ExceptionUtil.exception(ApiErrorCodeConstants.DOWNLOAD_FILE_FAILED, e.toString());
        }
    }

    /**
     * 大文件下载 - 分块传输
     *
     * @param filePath 文件路径
     * @param response response
     */
    public static void downloadLargeFile(String filePath, HttpServletResponse response) {
        File file = new File(filePath);

        if (!file.exists()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        try (RandomAccessFile randomFile = new RandomAccessFile(file, "r");
             OutputStream outputStream = response.getOutputStream()) {

            // 设置响应头
            setupResponseHeaders(response, file.getName(), file.length());

            // 分块传输
            byte[] buffer = new byte[8192]; // 8KB buffer
            long bytesRead;
            long totalBytesRead = 0;

            while (totalBytesRead < file.length()) {
                bytesRead = randomFile.read(buffer);
                if (bytesRead == -1) {
                    break;
                }

                outputStream.write(buffer, 0, (int) bytesRead);
                totalBytesRead += bytesRead;
                outputStream.flush();
            }

        } catch (IOException e) {
            log.error("caught an exception on downloadLargeFile", e);
            throw ExceptionUtil.exception(ApiErrorCodeConstants.DOWNLOAD_FILE_FAILED, e.toString());
        }
    }
}
